package cn.zj.smart.util.net;

import cn.zj.smart.log4j2.Log;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * http连接池
 */
public class HttpConnPool {
    public static final Logger logger = LogManager.getLogger();

    private PoolingHttpClientConnectionManager manager; //连接池管理类
    private ScheduledExecutorService monitorExecutor;
    private Config config;

    private CloseableHttpClient client;

    private HttpConnPool(Config config) {
        this.config = config;
        init();
    }

    /**
     * 创建连接池
     */
    public static HttpConnPool createPool(Config config) {
        if (config == null || config.idleTimeout < 1 || config.poolSize < 1 || config.maxRoute < 1) {
            return null;
        }
        return new HttpConnPool(config);
    }

    /**
     * 关闭连接池
     */
    public void closePool() {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        manager.close();
        monitorExecutor.shutdown();
    }

    /**
     * @param timeout 超时时间（单位：毫秒）。小于1表示不设置超时时间。
     */
    public String httpPost(String url, Map<String, String> mapParam, Map<String, String> heads, int timeout) {
        try {
            //装填参数
            List<NameValuePair> nvps = new ArrayList<>();
            if (mapParam != null) {
                for (String key : mapParam.keySet()) {
                    nvps.add(new BasicNameValuePair(key, mapParam.get(key)));
                }
            }

            HttpPost httpPost = new HttpPost(url);
            if (timeout > 0) {
                RequestConfig requestConfig = RequestConfig.custom()
                        .setConnectionRequestTimeout(timeout)
                        .setSocketTimeout(timeout)
                        .setConnectTimeout(timeout).build();
                httpPost.setConfig(requestConfig);
            }
            //设置参数到请求对象中
            httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
            //设置header信息
            //指定报文头【Content-type】、【User-Agent】
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
            if (heads != null) {
                for (String key : heads.keySet()) {
                    httpPost.addHeader(key, heads.get(key));
                }
            }
            HttpResponse result = client.execute(httpPost);
            url = URLDecoder.decode(url, "UTF-8");
            //请求发送成功，并得到响应
            if (result.getStatusLine().getStatusCode() == 200) {
                try {
                    //读取服务器返回过来的json字符串数据
                    String str = EntityUtils.toString(result.getEntity());
                    return str;
                } catch (Exception e) {
                    logger.error("post请求提交失败:" + url, e);
                }
            }
        } catch (IOException e) {
            logger.error("post请求提交失败:" + url, e);
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 发送get请求
     *
     * @param url     路径
     * @param timeout 超时时间（单位：毫秒）。小于1表示不设置超时时间。
     * @return
     */
    public String httpGet(String url, Map<String, String> heads, int timeout) {
        //get请求返回结果
        //        JSONObject jsonResult = null;
        try {
            //发送get请求
            HttpGet request = new HttpGet(url);
            request.setHeader("Connection", "close");
            if (timeout > 0) {
                RequestConfig requestConfig = RequestConfig.custom()
                        .setConnectionRequestTimeout(timeout)
                        .setSocketTimeout(timeout)
                        .setConnectTimeout(timeout).build();
                request.setConfig(requestConfig);
            }
            if (heads != null) {
                for (String key : heads.keySet()) {
                    request.addHeader(key, heads.get(key));
                }
            }
            CloseableHttpResponse response = client.execute(request);

            /**请求发送成功，并得到响应**/
            if (response.getStatusLine().getStatusCode() == 200) {
                /**读取服务器返回过来的json字符串数据**/
                String strResult = EntityUtils.toString(response.getEntity());
                /**把json字符串转换成json对象**/
//                jsonResult = JSONObject.fromObject(strResult);
                url = URLDecoder.decode(url, "UTF-8");
                return strResult;
            } else {
                logger.error("get请求提交失败:" + url);
            }
        } catch (Exception e) {
            logger.error("get请求提交失败:url={}, exception={}" , url, ""+e);
//            e.printStackTrace();
        } finally {
        }

        return null;
    }

    private void init() {
        ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
        //采用绕过验证的方式处理https请求
        SSLContext sslcontext = createIgnoreVerifySSL();
        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslcontext);
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainSocketFactory)
                .register("https", sslSocketFactory).build();
        manager = new PoolingHttpClientConnectionManager(registry);
        //设置连接参数
        manager.setMaxTotal(config.poolSize); // 最大连接数
        manager.setDefaultMaxPerRoute(config.maxRoute); // 路由最大连接数
        //开启监控线程,对异常和空闲线程进行关闭
        monitorExecutor = Executors.newScheduledThreadPool(1);
        monitorExecutor.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                manager.closeExpiredConnections();//关闭异常连接
                manager.closeIdleConnections(config.idleTimeout, TimeUnit.SECONDS);//关闭超时的空闲连接
            }
        }, config.idleTimeout, config.idleTimeout, TimeUnit.SECONDS);
        HttpClientBuilder clientBuilder = HttpClients.custom().setConnectionManager(manager);
        //请求失败时,进行请求重试
        if (config.retryTimes > 0) {
            HttpRequestRetryHandler handler = (e, i, httpContext) -> {
                if (i > config.retryTimes) {
                    //重试超过3次,放弃请求
                    logger.error("retry has more than {} time, give up request", config.retryTimes);
                    return false;
                }
                if (e instanceof NoHttpResponseException) {
                    //服务器没有响应,可能是服务器断开了连接,应该重试
                    logger.error("receive no response from server, retry");
                    return true;
                }
                if (e instanceof SSLHandshakeException) {
                    // SSL握手异常
                    logger.error("SSL hand shake exception");
                    return false;
                }
                if (e instanceof InterruptedIOException) {
                    //超时
                    logger.error("InterruptedIOException");
                    return false;
                }
                if (e instanceof UnknownHostException) {
                    // 服务器不可达
                    logger.error("server host unknown");
                    return false;
                }
                if (e instanceof ConnectTimeoutException) {
                    // 连接超时
                    logger.error("Connection Time out");
                    return false;
                }
                if (e instanceof SSLException) {
                    logger.error("SSLException");
                    return false;
                }
                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    //如果请求不是关闭连接的请求
                    return true;
                }
                return false;
            };
            clientBuilder.setRetryHandler(handler);
        }
        this.client = clientBuilder.build();
    }


    /**
     * 绕过验证
     *
     * @return
     */
    public static SSLContext createIgnoreVerifySSL() {
        try {
            SSLContext sc = SSLContext.getInstance("SSLv3");
            // 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(
                        java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                        String paramString) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(
                        java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                        String paramString) throws CertificateException {
                }
                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            sc.init(null, new TrustManager[]{trustManager}, null);
            return sc;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public static final class Config {

        private int poolSize = 1;

        private int maxRoute = 1;

        private int idleTimeout = 3;
        private int retryTimes = 0;

        /**
         * @param poolSize    http连接池的最大连接数(最多同时处理的请求数)。默认值1
         * @param maxRoute    单条链路最大连接数（一个ip+一个端口 是一个链路）。默认值1
         * @param idleTimeout 空闲连接超时时间,超时的空闲连接会被关闭，单位：秒。默认值3
         * @param retryTimes  请求失败的重试次数，0表示不重试
         */
        public Config(int poolSize, int maxRoute, int idleTimeout, int retryTimes) {
            this.poolSize = poolSize;
            this.maxRoute = maxRoute;
            this.idleTimeout = idleTimeout;
            this.retryTimes = retryTimes;
        }

    }

}